﻿using S7.Net;
using System;
using System.Collections.Generic;
using System.Text;

namespace S7NETPLC
{
    public class PlcHandle
    {
        public PlcHandle()
        {

        }
        /// <summary>
        /// 读取数据
        /// </summary>
        /// <param name="plc">读取数据的plc</param>
        ///// <param name="dataType">数据区</param>
        ///// <param name="db">DB块号，读写I、Q、M时都为0，读写V区的时为1，读写DB块时按实际情况处理</param>
        ///<param name="dataAddress">数据区(I、Q、M、V、DB)</param>
        ///<param name="dbNumber">当数据区为DB时使用此编号 数据区为I、Q、M时都为0,V区的时为1</param>
        /// <param name="startByteAdr">起始地址</param>
        //// <param name="varType">数据类型</param>
        /// <param name="resDataType">读取的数据类型(Bool,Byte,SByte,Word,Int,DWord,DInt,Real)</param>
        /// <param name="varCount">读取数量</param>
        /// <returns></returns>
        public static Response<string> ReadData(Plc plc, string dataAddress, int dbNumber, int startByteAdr, string resDataType, int varCount)
        {
            Response<string> res = new Response<string>();
            if (!plc.IsConnected)
            {
                res.reason = "plc未连接请先连接plc";
                return res;
            }
            //if (dataAddress != "I" || dataAddress != "Q" || dataAddress != "M" || dataAddress != "V" || dataAddress != "DB")
            //{
            //    res.reason = "请传入正确的数据区";
            //    return res;
            //}
            DataType dataType;//数据区
            int db;//DB块号
            VarType varType;//数据类型
            switch (dataAddress)
            {
                case "I":
                    dataType = DataType.Input;
                    db = 0;
                    break;
                case "Q":
                    dataType = DataType.Output;
                    db = 0;
                    break;
                case "M":
                    dataType = DataType.Memory;
                    db = 0;
                    break;
                case "V":
                    dataType = DataType.DataBlock;
                    db = 1;
                    break;
                case "DB":
                    dataType = DataType.DataBlock;
                    db = dbNumber;
                    break;
                default:
                    res.reason = "请输入正确的数据区";
                    return res;
            }
            switch (resDataType)
            {
                case "Bool":
                    //函数有bug，所以使用读取Byte函数，读取1个byte然后再计算出bit的值 
                    int bitNumber = 0;
                    var result = plc.Read(dataType, db, startByteAdr, VarType.Byte, varCount);
                    byte resultByte = (byte)result;
                    bool resultBool = resultByte.SelectBit(bitNumber); // SelectBit函数，从字节中读取1位
                    if (resultBool == true)
                    {
                        res.Body = "1";
                    }
                    if (resultBool == false)
                    {
                        res.Body = "0";
                    }
                    break;
                case "Byte"://无符号byte
                    var result_byte = plc.Read(dataType, db, startByteAdr, VarType.Byte, varCount);
                    byte read_byte = (byte)result_byte;
                    res.Body = read_byte.ToString();
                    break;
                case "SByte"://有符号byte
                    var result_sbyte = plc.Read(dataType, db, startByteAdr, VarType.Byte, varCount);
                    int read_result_sbyte = (byte)result_sbyte;
                    if (read_result_sbyte > 127)
                    {
                        read_result_sbyte = read_result_sbyte - 256;
                    }
                    sbyte read_sbyte = (sbyte)read_result_sbyte;
                    res.Body = read_sbyte.ToString();
                    break;
                case "Word"://无符号word
                    var result_word = plc.Read(dataType, db, startByteAdr, VarType.Word, varCount);
                    ushort read_word = (ushort)result_word;
                    res.Body = read_word.ToString();
                    break;
                case "Int"://有符号word
                    var result_int = plc.Read(dataType, db, startByteAdr, VarType.Int, varCount);
                    short read_int = (short)result_int;
                    res.Body = read_int.ToString();
                    break;
                case "DWord"://无符号DWord
                    var result_dword = plc.Read(dataType, db, startByteAdr, VarType.DWord, varCount);
                    uint read_dword = (uint)result_dword;
                    res.Body = read_dword.ToString();
                    break;
                case "DInt"://有符号DWord
                    //var result_dint = plc.Read(dataType, db, startByteAdr, VarType.DInt, 1);//这个函数有bug，只要是负数就显示-2147483648
                    var result_dint = plc.Read(dataType, db, startByteAdr, VarType.DWord, varCount);
                    double read_result_dint = (uint)result_dint;
                    if (read_result_dint > 2147483647)
                    {
                        read_result_dint = read_result_dint - 4294967296;
                    }
                    int read_dint = (int)read_result_dint;
                    res.Body = read_dint.ToString();
                    break;
                case "Real":
                    var result_real = plc.Read(dataType, db, startByteAdr, VarType.Real, varCount);
                    double read_real = (double)result_real;
                    res.Body = read_real.ToString();
                    break;
                default:
                    res.reason = "请输入正确的数据类型";
                    return res;
            }
            res.result = "OK";
            return res;
        }
        /// <summary>
        /// 写入数据
        /// </summary>
        /// <param name="plc">写入数据的plc</param>
        /// <param name="dataAddress">数据区(I、Q、M、V、DB)</param>
        /// <param name="dbNumber">当数据区为DB时使用此编号 数据区为I、Q、M时都为0,V区的时为1</param>
        /// <param name="startByteAdr">起始地址</param>
        /// <param name="reqDataType">写入的数据类型(Bool,Byte,SByte,Word,Int,DWord,DInt,Real)</param>
        /// <param name="writeData">写入的数据</param>
        /// <returns></returns>
        public static Response WriteData(Plc plc, string dataAddress, int dbNumber, int startByteAdr,string reqDataType, string writeData)
        {
            Response res = new Response();
            if (!plc.IsConnected)
            {
                res.reason = "plc未连接请先连接plc";
                return res;
            }
            //if (dataAddress != "I" || dataAddress != "Q" || dataAddress != "M" || dataAddress != "V" || dataAddress != "DB")
            //{
            //    res.reason = "请传入正确的数据区";
            //    return res;
            //}
            DataType dataType;//数据区
            int db;//DB块号
            VarType varType;//数据类型
            switch (dataAddress)
            {
                case "I":
                    dataType = DataType.Input;
                    db = 0;
                    break;
                case "Q":
                    dataType = DataType.Output;
                    db = 0;
                    break;
                case "M":
                    dataType = DataType.Memory;
                    db = 0;
                    break;
                case "V":
                    dataType = DataType.DataBlock;
                    db = 1;
                    break;
                case "DB":
                    dataType = DataType.DataBlock;
                    db = dbNumber;
                    break;
                default:
                    res.reason = "请输入正确的数据区";
                    return res;
            }
            switch (reqDataType)
            {
                case "Bool":
                    //public ErrorCode Write(DataType dataType, int db, int startByteAdr, object value)
                    //上面的函数写位数据有bug，实现不了，所以用下面的函数来写

                    ///使用下面的函数写位数据时就需要先读取1个字节，再将字节中的位，置0或1，再将该字节数据写回到PLC
                    int bit_number = 0;
                    var result_bool = plc.Read(dataType, db, startByteAdr, VarType.Byte, 1);
                    byte read_result_bool = (byte)result_bool;
                    int write_bit_number = Convert.ToInt16(bit_number) + 1;
                    byte write_bool = 0;
                    if (writeData == "1")
                    {
                        write_bool = set_bit(read_result_bool, write_bit_number, true);
                    }
                    if (writeData == "0")
                    {
                        write_bool = set_bit(read_result_bool, write_bit_number, false);
                    }
                    ///dataType：数据区
			        ///int db：DB块号，读写I、Q、M时都为0，读写V区的时为1，读写DB块时按实际情况处理
			        ///int startByteAdr：起始地址
			        ///write_bool：要写入的值，这个值的数据类型要与所选的数据区的数据类型相符
			        ///注：下面各case中函数意义相同
                    //var write_bool_result = plc.Write(dataType, db, startByteAdr, write_bool);
                    plc.Write(dataType, db, startByteAdr, write_bool);
                    break;
                case "Byte"://无符号byte
                    byte write_byte = Convert.ToByte(writeData);
                    //var write_byte_result = plc.Write(dataType, db, startByteAdr, write_byte);
                    plc.Write(dataType, db, startByteAdr, write_byte);
                    break;
                case "SByte"://有符号byte
                    sbyte write_sbyte = Convert.ToSByte(writeData);
                    //var write_sbyte_result = plc.Write(dataType, db, startByteAdr, (byte)write_sbyte);
                    plc.Write(dataType, db, startByteAdr, (byte)write_sbyte);
                    break;
                case "Word"://无符号Word
                    ushort write_word = Convert.ToUInt16(writeData);
                    //var write_word_result = plc.Write(dataType, db, startByteAdr, write_word);
                    plc.Write(dataType, db, startByteAdr, write_word);
                    break;
                case "Int"://有符号Word
                    short write_int = Convert.ToInt16(writeData);
                    //var write_int_result = plc.Write(dataType, db, startByteAdr, write_int);
                    plc.Write(dataType, db, startByteAdr, write_int);
                    break;
                case "DWord"://无符号DWord
                    uint write_dword = Convert.ToUInt32(writeData);
                    // var write_dword_result = plc.Write(dataType, db, startByteAdr, write_dword);
                    plc.Write(dataType, db, startByteAdr, write_dword);
                    break;
                case "DInt"://有符号DWord
                    int write_dint = Convert.ToInt32(writeData);
                    // var write_dint_result = plc.Write(dataType, db, startByteAdr, write_dint);
                    plc.Write(dataType, db, startByteAdr, write_dint);
                    break;
                case "Real":
                    double write_real = Convert.ToDouble(writeData);
                    // var write_real_result = plc.Write(dataType, db, startByteAdr, write_real);
                    plc.Write(dataType, db, startByteAdr, write_real);
                    break;
                default:
                    res.reason = "请输入正确的数据类型";
                    return res;
            }

            res.result = "OK";
            return res;

        }
        ///该函数用来对字节数据中的某一位置0或者置1
        ///byte data：要操作的字节数据
        ///int index：要操作位
        ///bool flag：置0或置1
        ///return：操作后得到的字节数据
        private static byte  set_bit(byte data, int index, bool flag)
        {
            // if (index > 8 || index < 1) 该程序中不存在这种情况，如果移植他处请注意。
            // throw new ArgumentOutOfRangeException();
            int v = index < 2 ? index : (2 << (index - 2));
            return flag ? (byte)(data | v) : (byte)(data & ~v);
        }
    }
}
